/**
 * File: DataStoreImpl.java
 * Created by: mhaimel
 * Created on: Apr 19, 2010
 * CVS:  $Id: DataStoreImpl.java 1.0 Apr 19, 2010 9:45:35 AM mhaimel Exp $
 */
package uk.ac.ebi.curtain.utils.data.impl;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;

import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.Mapper;
import uk.ac.ebi.curtain.utils.data.DataEntry;
import uk.ac.ebi.curtain.utils.data.DataStore;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.MetaData;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.EntryMapper;
import uk.ac.ebi.curtain.utils.file.EntryMapperBean;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.file.ParseUtil;
import uk.ac.ebi.curtain.utils.io.impl.BufferedRaf;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;

/**
 * @author mhaimel
 *
 */
public class DataStoreImpl implements DataStore<EntryMapperBean> {
	
	private final GenericDataHolder<File> data;
	
	public DataStoreImpl(MetaData meta, File ... arr) {
		this.data = new GenericDataHolder<File>(meta, arr);
	}
	
	public DataStoreImpl(DataEntry<File> data) {
		this(data.getMetaData(),data.getEntries());
	}

	@Override
	public File[] getFiles() {
		return this.data.getEntries();
	}

	@Override
	public MetaData getMetaData() {
		return this.data.getMetaData();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void parse(Mapper<DataEntry<EntryMapperBean>> mapper) {
		int length = getFiles().length;
		if(length == 1){
			parseSingleFile(mapper);
		} else if(length == 2){
			if(getReadType().isPaired()){
				parseMultipleFiles(mapper);
			} else {
				throw new IllegalArgumentException("Multiple files only allowed for Paired entries, and not for " + getReadType());
			}
		} else {
			throw new IllegalArgumentException("Only one or two (paired) files allowed instead of " + length + ": " + Arrays.toString(getFiles()));
		}
	}
	
	private ReadType getReadType() {
		return getMetaData().getReadType();
	}

	private FileType getFileType() {
		return getMetaData().getFileType();
	}

	private void parseMultipleFiles(Mapper<DataEntry<EntryMapperBean>> mapper) {
		File[] entries = this.data.getEntries();
		int length = entries.length;
		if(length != 2) throw new IllegalArgumentException("Maximum of two files supported: " + length + ": " + Arrays.toString(entries));
		ParseUtil parser = this.getFileType().getParser();
		FileInfo[] info = new FileInfo[length];
		BufferedRaf[] arr = new BufferedRaf[length];
		TmpMapper[] mapArr = new TmpMapper[length];
		try {
			for(int i = 0; i < length; ++i){
				info[i] = new FileInfo(entries[i], getFileType(), getReadType());
				mapArr[i] = new TmpMapper();
				arr[i] = new FileIO(entries[i]).getBufferedRafReader();
			}
			while(true){
				// don't use for each loop -> slows things down
				parser.parseNEntry(1, arr[0], info[0], mapArr[0]);
				parser.parseNEntry(1, arr[1], info[1], mapArr[1]);
				EntryMapperBean a = mapArr[0].reset();
				EntryMapperBean b = mapArr[1].reset();
				if(a == null || b == null){
					// EOF
					if(a != b){ // one finished, the other hasn't
						throw new IllegalStateException("Unequal amount of entries in files: "+a+" <?> "+b+"!! " + Arrays.toString(info)); 
					}
					break;
				}
				mapper.map(new GenericDataHolder<EntryMapperBean>(getMetaData(), a,b));
			}			
		} catch (IOException e) {
			throw new CurtainUncheckedException(e);
		} finally {
			for(int i = 0; i < length; ++i){
				FileIO.closeQuietly(arr[i]);
			}
		}
	}

	private void parseSingleFile(Mapper<DataEntry<EntryMapperBean>> mapper) {
		File file = this.data.getEntries()[0];
		ParseUtil parser = this.getFileType().getParser();
		FileInfo info = new FileInfo(file,getFileType(),getReadType());
		if(getReadType().isPaired()){
			parseSinglePairedFile(mapper, parser, info);
		} else {
			parseSingleUnpairedFile(mapper, parser, info);
		}

	}


	private void parseSingleUnpairedFile(final Mapper<DataEntry<EntryMapperBean>> mapper,ParseUtil parser, FileInfo info) {
		parser.parse(info, new EntryMapper() {
			
			@Override
			public void mapEntry(EntryMapperBean bean) {
				mapper.map(new GenericDataHolder<EntryMapperBean>(getMetaData(), bean));
			}
		});
	}

	private void parseSinglePairedFile(Mapper<DataEntry<EntryMapperBean>> mapper, ParseUtil parser, FileInfo info) {
		SingeFilePairedMapper sMapper = new SingeFilePairedMapper(getMetaData(),mapper);
		parser.parse(info,sMapper);
		EntryMapperBean remaining = sMapper.getEntry();
		if(remaining != null){
			throw new IllegalStateException("Uneven entries in paired file "+ info);
		}
	}

	private static class SingeFilePairedMapper implements EntryMapper {
		
		private final AtomicReference<EntryMapperBean> ref;
		private final MetaData meta;
		private final Mapper<DataEntry<EntryMapperBean>> mapper;

		public SingeFilePairedMapper(MetaData metaData,Mapper<DataEntry<EntryMapperBean>> mapper) {
			this.ref = new AtomicReference<EntryMapperBean>(null);
			this.mapper = mapper;
			this.meta = metaData;
		}

		@Override
		public void mapEntry(EntryMapperBean bean) {
			EntryMapperBean prev = this.ref.getAndSet(null);
			if(null == prev){
				this.ref.set(bean);
			} else {
				this.mapper.map(new GenericDataHolder<EntryMapperBean>(this.meta, prev,bean));
			}
		}
		
		public EntryMapperBean getEntry(){
			return this.ref.get();
		}
		
	}

	private static class TmpMapper implements EntryMapper {
		
		private final AtomicReference<EntryMapperBean> ref;

		public TmpMapper() {
			this.ref = new AtomicReference<EntryMapperBean>(null);
		}

		@Override
		public void mapEntry(EntryMapperBean bean) {
			this.ref.set(bean);
		}
		
		public EntryMapperBean reset(){
			return this.ref.getAndSet(null);
		}
		
	}


}
